iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
IT 管理

Backstage : 打造企業內部開發者整合平台系列 第 10

Day 10 : Backstage 內部員工身份認證:從 AD 同步到 SSO 的實施之路 - 遷移插件篇

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240919/20128232zuk5rORJPp.png

簡介

在上一篇文章中,我們介紹了如何在舊版 Backstage 中添加 OIDC 插件,並為 Backstage 設定了 SSL 保護以支援 HTTPS,為後續的 IdentityServer 設定做好準備。在本篇文章中,我們將深入探討如何將 OIDC 插件遷移到新版 Backstage 系統,比較新舊版架構在插件應用上的差異。除了將插件與 IdentityServer 結合外,我們還會撰寫解析器邏輯,以便在 OIDC 登入後正確對應到先前抓取到的 AD 員工實體。

新舊版後端上的差異

根據上一篇在安裝舊版 OIDC 插件時的架構,我們完成了以下幾個重要步驟:

  1. 在前端部分,我們在 packages/app/src/apis.ts 中定義了 OIDC API,並引用了必要的基底功能和 API 調用端口。此外,我們還在 App.tsx 中新增了 OIDC 的登入選項。
  2. 在後端部分,我們在 packages/backend/src/plugins/auth.ts 中實現了 OIDC 的登入功能和解析器,並在 index.ts 中啟用了這些功能。需要注意的是,index.ts 包含了相當冗長且複雜的設定檔,並引入了自訂插件 auth.ts

清楚劃分了前端和後端的職責,一切看起來如此合理。

backstage # 專案目錄
├── packages
│   ├── app
│   │   └── src
│   │       ├── api.ts  # 定義 OIDC 的 API 方法
│   │       └── App.tsx  # 新增 OIDC 的登入選項介面
│   └── backend
│       └── src
│           ├── plugins
│           │   └── auth.ts  # 設定插件並定義解析器
│           └── index.ts  # 啟用插件
│ 
└── app-config.yaml

而在新版後端架構一切更為簡潔合理,對於我這種強迫症來說,更喜歡新版的整潔收納。首先將原本包含在 backend/src/plugins 中的一個一個自定插件或模組,轉移到了專案資料夾下的 plugins/ 放置,並且每一個模組有自己的資料夾與完整的前端、依賴、註冊、測試、路由的設定,而這些東西都可以通過指令來自動產生,我們將會在後面演示一次。

backstage 
├── packages
│   ├── app # 維持不變
│   │
│   └── backend
│				└── src
│						└── index.ts # 加入插件方式改變
├── app-config.yaml
└── plugins
		├── example-plugin
		├── example-plugin2
		└── example-plugin3-backend

由於經過更完整的封裝等操作,我們現在加入插件只需在 backend/src/index.ts 加入一行就可以完成,例如下面這樣的效果。每一個插件都有自己的獨立空間與完善的架構,並且支援快速部署到 npm 空間,也就是說我們也可以將其他人的插件透過 git 抓下來修改,最後一樣加入一行程式啟用,大大增強了開發插件的拓展性與靈活性。

backend.add(import('@internal/backstage-plugin-outlook-api-backend'));

但這也意味著,所有插件都得再經過一定程度的更新修改,才能支援現今的 Backstage。自從推出新版架構後,不管是官方文件或是網路上的討論,時常因過時或不明原因無法使用,由於架構的特殊性,許多錯誤很難知道是什麼問題,只能找官方開發者詢問或是等待 Backstage 更新。這是開發 Backstage 的過程中最花時間、體驗最差的問題,並且開源的特性,每個插件的品質參差不齊。

插件轉接器

根據官方的說法與自己實測的結果,如果某些插件尚未遷移到新版系統,可以嘗試這個方式,將舊版插件的寫法轉換成新版系統能讀取的形式,這個轉換器並不總是適用於所有插件,某些特別的插件可能會依賴特定功能,這可能導致在轉換過程中缺少部分實作或參數

這邊舉例 OIDC 插件在測試時,如果透過直接轉換的樣子如下 :

import { legacyPlugin } from '@backstage/backend-common';

const backend = createBackend();
backend.add(legacyPlugin('auth', import('./plugins/auth')));
backend.start();

當時我想,要是能夠直接通過這個轉換就好了。但是,由於該插件是基於 auth 功能擴展的,所以在 Backstage 啟動時,登入相關的 log 記錄會顯示其操作都是由 auth 模組執行的。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232erN3BaKLek.jpg
legacyPlugin 中,第一個參數 auth 就是該插件的名稱。當使用登入驗證相關功能時,Backstage 預設會從 auth 插件中尋找對應的功能。不幸的是,如果 OIDC 插件以這種方式加入 Backstage,會與新版的預設驗證模組發生衝突,導致系統提示你重複引用名為 auth 的模組。你可能會想到直接更改名稱,由於預設的運作邏輯,Backstage會 auth 模組中找不到 OIDC 插件設定,將導致插件無法生效。

// auth plugin
backend.add(import('@backstage/plugin-auth-backend'));

為了解決這類問題,許多插件已經進行了更新。根據官方文件,大多數插件在引入新版系統時,會在名稱末尾加上 /alpha,例如以下這個插件:

backend.add(import('@backstage/plugin-catalog-backend-module-aws/alpha'));

既然沒有現成的遷移版本,很幸運的我們能夠自行遷移到新版系統,更棒的是在遷移 OIDC 插件的過程,我們並不需要修改模組的程式,也非常適合用來小試身手。

新增插件拓展 Auth 模組

現在我們回到新版系統上的專案目錄,透過 yarn new 指令來新增一個插件,可以看到 Bcakstage 提供了許多開發範本,只需要命名就可以自動產生完畢相關需要的組件與設定,讓我們可以專注在開發插件功能,這邊我們選擇 backend-module ,來幫 auth 模組拓展加入 OIDC 功能。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232hZ4jfu4wsB.png
第一個輸入要拓展的既有模組 auth,再來輸入自己命名的插件名稱,我命名為 oidc ,接著 Backstage 就會自動產生檔案並安裝好依賴,順利的話就會跳出成功訊息,並且看到左邊的 plugins 資料夾多出了剛裝好的插件。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232Egus8jDB0T.png
最新版的 Backstage 會自動將插件加入到 backend/src/index.ts 中。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232qDFOvlTNDA.png
進到剛剛新增的插件其中的module.ts ,這是預設的範本程式可以看到這個架構其實與舊版非常相似。接下來我們要加入 OIDC 的功能到 Backstage 中。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232RQJQuAmN8v.png
幸好我們可以在文件中找到別的插件的結構,同為拓展 Auth 功能,所以我們可以很輕易的模仿 OIDC 的版本。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232bvWqoWvH0r.png

創建新版 OIDC 插件

根目錄輸入指令安裝 OIDC 身份驗證模組

yarn --cwd packages/backend add @backstage[/plugin-auth-backend-module-oidc-provider](https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider)

新增到 packages/backend/src/index.ts

backend.add(import('@backstage/[plugin-auth-backend-module-oidc-provider](https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider)'));

再來就是參考前一篇的部分,在 apis.ts 定義 API,在對應的地方加入以下程式碼,並 import 參考的功能。

import {
  AnyApiFactory,
  ApiRef, //new
  BackstageIdentityApi, // new
  configApiRef,
  createApiFactory,
  createApiRef, discoveryApiRef, oauthRequestApiRef, // new
  OpenIdConnectApi, // new
  ProfileInfoApi, // new
  SessionApi, // new
} from '@backstage/core-plugin-api';
import { OAuth2 } from '@backstage/core-app-api'; // new
...

export const identityserverOIDCAuthApiRef: ApiRef<
  OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
  id: 'auth.sso-auth-provider',
});

...

  createApiFactory({
    api: identityserverOIDCAuthApiRef,
    deps: {
      discoveryApi: discoveryApiRef,
      oauthRequestApi: oauthRequestApiRef,
      configApi: configApiRef,
    },
    factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
      configApi,
      discoveryApi,
      oauthRequestApi,
      provider: {
        id: 'sso-auth-provider',
        title: 'SSO auth provider',
        icon: () => null,
      },
      environment: configApi.getOptionalString('auth.environment'),
      defaultScopes: ['openid', 'profile', 'offline_access'],
      popupOptions: {
        // optional, used to customize login in popup size
        size: {
          fullscreen: true,
        },
        /**
         * or specify popup width and height
         * size: {
            width: 1000,
            height: 1000,
          }
         */
      },
    }),
  }),

接著我們來改寫剛剛新增的擴充插件邏輯 plugins/auth-backend-module-oidc/src/module.ts

import {
  coreServices,
  createBackendModule,
} from '@backstage/backend-plugin-api';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import {
  authProvidersExtensionPoint,
  createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import {
  DEFAULT_NAMESPACE,
  stringifyEntityRef,
} from '@backstage/catalog-model';

export const authModuleOidc = createBackendModule({
  pluginId: 'auth',
  moduleId: 'oidc',
  register(reg) {
    reg.registerInit({
      deps: {
        providers: authProvidersExtensionPoint,
        logger: coreServices.logger,
      },
      async init({ providers, logger }) {
        providers.registerProvider({
          providerId: 'sso-auth-provider',
          factory: createOAuthProviderFactory({
            authenticator: oidcAuthenticator,
            async signInResolver(info, ctx) {
              logger.info('正在使用 OIDC 身份驗證');
              const userRef = stringifyEntityRef({
                kind: 'User',
                name: info.result.fullProfile.userinfo.sub,
                namespace: DEFAULT_NAMESPACE,
              });
              return ctx.issueToken({
                claims: {
                  sub: userRef, 
                  ent: [userRef], 
                },
              });
            },
          }),
        });
      },
    });
  },
});

為後端加入 OIDC 插件與我們展的模組結果應會長這樣,基礎的 auth 插件是不能刪除的,下面的 github、oidc 驗證提供者插件基本上都是基於 auth 去擴充。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232Y6Cssi0zFw.png
最後加入設定到 app-config.yaml ,以及新增登入選項的介面到 App.tsx ,詳細可以參考上一篇的程式碼,這邊先以截圖結果的樣子呈現。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232zabj4YdKNG.png
https://ithelp.ithome.com.tw/upload/images/20240919/20128232hXEu55fifB.png

設定 IdentityServer 身份驗證服務

接下來是實做身份驗證服務的部分,透過 IdentityServer 提供的其中一個 QuickStart 範例專案,我們可以快速啟用一個最基本的 OIDC 驗證,我們將使用它來測試串接 Backstage 做登入功能。

對於單獨下載 github 中某個資料夾或檔案,推薦使用此工具 DownGit,可以直接將 IdentityServer QuickStart 的網址貼進去單獨下載。

https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/2_InteractiveAspNetCore

這個範例中包含了 IdentityServer 的 Server 端,也包含了讓我們快速測試的 client 端,讓我們直接啟用專案就可以體驗 SSO 的登陸過程,所以我們可以把 clinet 端的部分改成連結我們 Backstage 服務,您只需要修改Config.cs即可,這裡範本給了兩種 client 登入驗證方式,要實作使用登入效果我們選擇下方的 code 驗證流程。

確保RedirectUris設定為https://localhost:7007/api/auth/sso-auth-provider/handler/frame ,這是 Backstage OIDC 的預設值,其他值可以按照圖中所例。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232iBbb0plAPF.png
設定完畢後可以將 IdentityServer 與 Backstage 同時啟用,登入 Backstage 選項選擇 OIDC 並填入IdentityServer 預設的範例使用者帳密 bob/bob ,恭喜我們終於完成了整個步驟。
https://ithelp.ithome.com.tw/upload/images/20240919/20128232aoW97z7k6G.png

結論

在本篇介紹了 Backstage 在新舊版系統下的差異,這點在開發插件時時常發生,但隨著 Backstage 不斷更新都以新版系統為主,我們只了解其中的變化與處理方式,例如本文的 OIDC 舊插件遷移的案例,相信在社群的不斷更新下,未來也不需要經過這樣的操作。

至此,關於 OIDC 插件的演示就到此結束,我們已成功為 Backstage 增加了 SSO 功能。未來在將其他平台服務整合至 Backstage 時,可以透過這一 SSO 機制,實現只需登入 Backstage,即可在結合的其他平台中使用相同的身份驗證。

關於 Backstage 的新舊版文件遷移狀態可以參考這個 issue,在開發時隨時關注最新的進展,也許可以幫助自己省下非常多時間~
https://ithelp.ithome.com.tw/upload/images/20240919/20128232iuF97UmoOh.png

參考文獻

https://backstage.io/docs/auth/oidc/
https://medium.com/@jincoco/backstage-io-oidc-authentication-with-duende-identityserver-a6c076eb69d0
https://www.youtube.com/watch?v=Xncis04BhQs
https://backstage.io/docs/backend-system/building-backends/migrating
https://medium.com/@jincoco/backstage-io-migrating-oidc-auth-module-to-the-new-backend-system-aa9e12786204
https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider
https://minhaskamal.github.io/DownGit


上一篇
Day 9 : Backstage 內部員工身份認證:從 AD 同步到 SSO 的實施之路 - OIDC 篇
下一篇
Day 11 : Backstage 插件開發 - 初探 iframe 嵌入 Uptime Kuma 監控服務
系列文
Backstage : 打造企業內部開發者整合平台30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言